package simulationEngine;

import homeMadeExceptions.NetworkException;

import java.util.Vector;

import datatypes.LogicalNetworkActionType;
import datatypes.NetworkAction;
import datatypes.PhysicalNetworkActionType;

public class TransportLayerScheduler {

	//Variables
	private Vector<NetworkAction> LayAct = null;
	private ApplicationLayerScheduler als = null;
	private SimulationEngine engine = null;
	
	//private boolean withLostGuarantee = true;
	//private boolean withReceiveReply = true;

	
	//Methods
	
	public TransportLayerScheduler(SimulationEngine engine, ApplicationLayerScheduler als){
		this.engine = engine;
		this.als = als;
	}

	
	public void init(){
		this.importTraLayActions();
	}
	
	//This method initialises the Actions should be done in next simulationstep
	//the content comes from the Simulation Enviroment, where the processer has
	//updated the steps in the TraLayerParameter
	private void importTraLayActions() {
		this.LayAct = engine.getSimulationEnviroment().getQueue().getActions();
	}

	//for internal use, to get the Actions 	
	private Vector<NetworkAction> getTraLayActions(){
		if (LayAct == null){
			LayAct = new Vector<NetworkAction>();	
			this.importTraLayActions();
		}
		return LayAct;
	}
	
	//Every Action with Step Id has the same priority, so they will be executed in this step
	//its for simulating parallelity
	public void simulateNext(int StepID) {
		int size = this.getTraLayActions().size();
		// Physical Network Step should executed
		for (int i = 0; i < size; i++) {
			this.execute(this.getTraLayActions().get(i));
		}
		LayAct = null;
		engine.getScheduler().invokeStepComplete(StepID);
	}
	
	//for execute an single NetworkAction
	private void execute (NetworkAction action){
		if (action.isAppLayerAffected()){
			als.execute(action);
		}
		switch (action.getPhyType()){
		//Neighbour to Neighbour Communication if there will be send an signal to als is 
		//decided by the type of the Message
		case SEND_MESSAGE:
			try {
					engine.getMiniworld().sendPhysicalMessage(action.getAffectedHost1(action.getCurrentStepNumber()), action.getAffectedHost2(action.getCurrentStepNumber()));
					this.appendNextAction(this.calcNextNeededStep(action, true));
				} catch (NetworkException e) {
					this.appendNextAction(this.calcNextNeededStep(action, false));
				}
			break;
		//Every sended Message between physical hosts is closed with an Receive Message
		case RECEIVE_MESSAGE:
			try {
				engine.getMiniworld().recvPhysicalMessage(action.getMessage(), action.getAffectedHost2(action.getCurrentStepNumber()), action.getAffectedHost1(action.getCurrentStepNumber()));
				this.appendNextAction(this.calcNextNeededStep(action, true));
			} catch (NetworkException e) {
				this.appendNextAction(this.calcNextNeededStep(action, false));
			}
			break;
		//Every lost Message between physical hosts, whether if it has logical source or not is closed with an Message_Lost
		case MESSAGE_LOST:
			//Message LOST on physical Layer is already recognized
			//now its only interesting if a signal to initial Sender should
			//sended --> AppLayerScheduler
			break;
		case SEND_MSG_LOST:
			try {
				engine.getMiniworld().sendPhysicalMessage(action.getAffectedHost2(action.getCurrentStepNumber()), action.getAffectedHost1(action.getCurrentStepNumber()));
				this.appendNextAction(this.calcNextNeededStep(action, true));
			} catch (NetworkException e) {
				this.appendNextAction(this.calcNextNeededStep(action, false));
			}
			break;
		//Every sended Message between logical hosts , an is arrived (received at an intermediate) will sended on to
		//next intermediate 	
		case PASS_ON_MESSAGE:
			try {
				engine.getMiniworld().sendPhysicalMessage(action.getAffectedHost1(action.getCurrentStepNumber()), action.getAffectedHost2(action.getCurrentStepNumber()));
				this.appendNextAction(this.calcNextNeededStep(action, true));
			} catch (NetworkException e) {
				this.appendNextAction(this.calcNextNeededStep(action, false));
			}
		break;
		case DO_NOTHING:
			//nothing to do
			break;
		}
	}
	
	private NetworkAction calcNextNeededStep (NetworkAction eA, boolean wasSuccesful){
		switch (eA.getPhyType()){
		case SEND_MESSAGE:
			if (wasSuccesful){
				if (eA.isTargetReached()) {
					eA.changeTypeOfAction(PhysicalNetworkActionType.DO_NOTHING, LogicalNetworkActionType.RECEIVE_MESSAGE);
				} else {
					eA.changeTypeOfAction(PhysicalNetworkActionType.PASS_ON_MESSAGE, LogicalNetworkActionType.TRACKING_MESSAGE);
					eA.count();
				}
			} else {
				eA.changeTypeOfAction(PhysicalNetworkActionType.MESSAGE_LOST, LogicalNetworkActionType.MESSAGE_LOST);
			}
			return eA;
		case RECEIVE_MESSAGE:
			if (wasSuccesful){
				if (eA.isSourceReached()){
					eA.changeTypeOfAction(PhysicalNetworkActionType.DO_NOTHING, LogicalNetworkActionType.SEND_SIGNAL_MSG_RECEIVED);
				} else {
					eA.uncount();
				}
			} else {
				eA.changeTypeOfAction(PhysicalNetworkActionType.DO_NOTHING, LogicalNetworkActionType.SEND_SIGNAL_ACK_LOST);
				eA.count();
			}
			return eA;
		case MESSAGE_LOST:
			//should never executed
			System.out.println("This should never been read.");
			return eA;
		case SEND_MSG_LOST:
			if (wasSuccesful){
				if (eA.isSourceReached()){
					eA.changeTypeOfAction(PhysicalNetworkActionType.DO_NOTHING, LogicalNetworkActionType.SEND_SIGNAL_MSG_LOST);
				} else {
					eA.uncount();
				}
			} else {
				eA.changeTypeOfAction(PhysicalNetworkActionType.DO_NOTHING, LogicalNetworkActionType.TIMEOUT);
				eA.count();
			}
			return eA;
		case PASS_ON_MESSAGE:
			if (wasSuccesful){
				if (eA.isTargetReached()){
					eA.changeTypeOfAction(PhysicalNetworkActionType.DO_NOTHING, LogicalNetworkActionType.RECEIVE_MESSAGE);
				} else {
					eA.changeTypeOfAction(PhysicalNetworkActionType.PASS_ON_MESSAGE, LogicalNetworkActionType.TRACKING_MESSAGE);
					eA.count();
				}
			} else {
				eA.changeTypeOfAction(PhysicalNetworkActionType.MESSAGE_LOST, LogicalNetworkActionType.MESSAGE_LOST);
				eA.uncount();
			}
			return eA;
		} 
		System.out.println("This also should never been read.");
		return null;
	}
	
	private void appendNextAction (NetworkAction nextAction){
		engine.getSimulationEnviroment().getQueue().addActionForNextStep(nextAction);
	}
}
